package com.yc.snack.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.PageContext;

import com.jspsmart.upload.File;
import com.jspsmart.upload.Files;
import com.jspsmart.upload.Request;
import com.jspsmart.upload.SmartUpload;

/**
 * 请求参数工具类
 * @author asus
 *
 */
public class RequestParamUtil {
	/**
	 * 将请求中的参数转成cls对象返回
	 * @param <T>
	 * @param cls
	 * @param request
	 * @return
	 */
	public static <T> T getParams(Class<T> cls, HttpServletRequest request) {
		T t = null;
		
		try {
			//1、获取请求中的所有参数名name=value aname=navy&pwd=123
			Enumeration<String> names = request.getParameterNames();
			//2、获取给定类中的所有setter方法
			Method[] methods = cls.getDeclaredMethods();
			Map<String, Method> setters = new HashMap<String, Method>();
			String methodName = null;
			for(Method method : methods) {
				methodName = method.getName();	//获取当前方法的方法名
				if(methodName.startsWith("set")) {	//如果方法是set开头，则我们认为1是一个setter
					setters.put(methodName, method);
				}
			}
			//3、根据请求中的参数名从setter方法中查找对应的方法->name值的第一个字母大写前面加上set setAname()
			String name = null;
			Method method = null;
			Class<?>[] types = null;
			Class<?> type = null;
			
			t = cls.newInstance();	//实例化对象
			String value = null;
			while (names.hasMoreElements()) {	//循环所有的name名
				name = names.nextElement();	//从枚举中获取下一个name值
				methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
				
				//根据方法名从setters方法中查找这个处理方法
				method = setters.get(methodName);
				if(method == null) {	//如果找不到这个处理方法
					continue;
				}
				
				//如果有这个方法，为了能够正确的激活方式注值，需要先获取这个方法的形参类型
				types = method.getParameterTypes();	//获取方法的形参列表类型
				if(types == null || types.length <= 0) {	// 说明这个方法没有形参列表，则没法注值
					continue;
				}
				
				//如果有，规范的setter方法也只能有一个参数，对应一个属性
				type = types[0];	//获取第一个形参的类型
				value = request.getParameter(name);	//先获取这个参数的值
				
				if(Integer.class == type) {	//如果形参类型是整形，说明是Integer
					method.invoke(t, Integer.valueOf(value));
				}else if(Integer.TYPE == type) {	//说明是int
					method.invoke(t, Integer.parseInt(value));
				}else if(Double.class == type) {	//如果形参类型是整形，说明是Double
					method.invoke(t, Double.valueOf(value));
				}else if(Double.TYPE == type) {	//说明是double
					method.invoke(t, Double.parseDouble(value));
				}else if(Long.class == type) {	//如果形参类型是整形，说明是Long
					method.invoke(t, Long.valueOf(value));
				}else if(Long.TYPE == type) {	//说明是long
					method.invoke(t, Long.parseLong(value));
				}else {
					method.invoke(t, value);
				}
			}
			//4、激活对象中的这个方法注值
			//(1) 先实例化一个这样的对象newInstance()
			//(2) method.invoke(obj, args) -> 先获取这个方法的形参类型
			//(3) 从请求中根据name获取这个形参值，转成这个方法需要的类型
			//(4) invoke()
			//5、所有属性注值完成后，返回这个对象
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return t;
	}
	
	//允许上传的文件类型
	private static final String ALLOWLIST = "gif,jpg,png,jpeg,doc,docx,xls,xlsx,txt,zip,rar,mp3,mp4";
	private static int MAXFILESIZE = 10 * 1024 * 1024;	//单个文件的最大大小10M
	private static int TOTALMAXSIZE = 10 * 1024 * 1024;	// 每次请求的最大总大小
	
	
	/**
	 * 将请求中的参数转成cls对象返回，带文件上传
	 * @param <T>
	 * @param cls
	 * @param pageContext
	 * @return
	 */
	public static <T> T getParams(Class<T> cls, PageContext pageContext) {
		T t = null;
		
		return t;
	}
	
	/**
	 * 处理带文件上传的请求
	 * @param pageContext
	 * @return	map 以表单元素name属性的值为键，以文件在服务器中的地址为值
	 * @throws Exception
	 */
	public static Map<String, String> upload(PageContext pageContext) throws Exception{
		Map<String, String> result = new HashMap<String, String>();
		
		//1、实例化组件
		SmartUpload su = new SmartUpload();
		
		//2、初始化组件
		su.initialize(pageContext);
		
		//3、设置文件上传的参数编码集、文件大小，允许或拒绝上传的文件类型等
		su.setCharset("utf-8"); 	//编码集
		su.setAllowedFilesList(ALLOWLIST); 	// 允许上传的文件后缀列表
		su.setMaxFileSize(MAXFILESIZE); 	//单个文件的最大值
		su.setTotalMaxFileSize(TOTALMAXSIZE); 	//每次请求的总大小
		
		//4、文件上传
		su.upload();
		
		//5、组件会将HttpServletRequest对象转成Request对象
		Request request = su.getRequest();	// 这个请求对象中存放的是普通表单元素，即除type="file"类型的
		
		//6、处理普通的表单元素数据。问题：这个时候我们是封装，并不知道每次请求会有哪些参数过来，即name属性的值我不知道
		Enumeration<String> names = request.getParameterNames();
		String name = null;
		//循环根据name的值
		while(names.hasMoreElements()) {
			name = names.nextElement();
			result.put(name, request.getParameter(name));
		}
		
		//7、处理上传的文件
		// 获取所有上传的文件
		Files files = su.getFiles();	//注意，这个File不是IO的而是smartupload中的
		if(files == null || files.getCount() <= 0 || files.getSize() <= 0) {	//说明没有上传文件
			return result;
		}
		
		//如果带文件，这需要循环处理。注意不是一个input type = "file"中有多个文件，而是表单中有多个type="file"
		Collection<File> fls = files.getCollection();	//返回这个对象的集合表示，这样就好循环。这个File也不是io中的,而是smartupload中
		String fieldName = null;	//表单中name属性的值
		String filePath = null;		//文件保存路径
		String temp = null;
		String fileStr = "";
		
		for (File fl : fls) {
			if(! fl.isMissing()) {	//如果文件上传过程中没有丢失数据
				temp = fl.getFieldName();
				
				if(StringUtil.checkNull(fieldName)) {	//说明此时读取的是第一个文件
					fieldName = temp;
				}else {	// 说明不是第一个文件，这需要判断是不是跟上一个文件属于同一个文本框
					if(!temp.equals(fieldName)) {	// 说明换了一个文本框了
						result.put(fieldName, fileStr);
						fileStr = "";
						fieldName = temp;	// fieldName指向新的文本框的值
					}
				}
				
				//处理文件上传 -> 重命名文件，避免文件重名
				filePath = ConstantInfo.uploadpath + "/" + System.currentTimeMillis() + "_" + fl.getFileName();
				
				//上传，将文件存到指定的路径，注意这个路径必须是一个绝对路径
				fl.saveAs(ConstantInfo.basepath + "/" + filePath);
				
				//给图片添加水印
				//WaterMarkUtil.textWaterMark(new java.io.File(ConstantInfo.basepath + "/" + filePath));
				WaterMarkUtil.textWaterMark(new java.io.File(ConstantInfo.basepath + "/" + filePath));
				
				if(StringUtil.checkNull(fileStr)) {
					fileStr = filePath;
				}else {
					fileStr += ";" + filePath;
				}
			}
		}
		result.put(fieldName, fileStr);	// 这个事处理文件最后一个文本框中的数据
		return result;
	}
	
	/**
	 * 处理带文件上传的请求
	 * @param pageContext
	 * @return	map 以表单元素name属性的值为键，以文件在服务器中的地址为值
	 * @throws Exception
	 */
	public static <T> T upload(Class<T> cls, PageContext pageContext) throws Exception{
		//实例化对象
		T t = cls.newInstance();
		
		//1、实例化组件
		SmartUpload su = new SmartUpload();
		
		//2、初始化组件
		su.initialize(pageContext);
		
		//3、设置文件上传的参数编码集、文件大小，允许或拒绝上传的文件类型等
		su.setCharset("utf-8"); 	//编码集
		su.setAllowedFilesList(ALLOWLIST); 	// 允许上传的文件后缀列表
		su.setMaxFileSize(MAXFILESIZE); 	//单个文件的最大值
		su.setTotalMaxFileSize(TOTALMAXSIZE); 	//每次请求的总大小
		
		//4、文件上传
		su.upload();
		
		//5、组件会将HttpServletRequest对象转成Request对象
		Request request = su.getRequest();	// 这个请求对象中存放的是普通表单元素，即除type="file"类型的
		
		//6、处理普通的表单元素数据。问题：这个时候我们是封装，并不知道每次请求会有哪些参数过来，即name属性的值我不知道
		Enumeration<String> names = request.getParameterNames();
		String name = null;
		//根据请求中的name,将对应的值注入到t对象的属性中 -> 获取这个类中的所有setter方法
		Method[] methods = cls.getDeclaredMethods();
		Map<String, Method> setter = new HashMap<String, Method>();	// setter
		
		//从所有方法中提取出setter方法 -> 以set开头的方法
		for(Method method : methods) {
			name = method.getName();
			if(name.startsWith("set")) {
				setter.put(name, method);
			}
		}
		
		//注值 -> name的第一个字母大写，前面加上set找对应的setter方法
		//循环所有的name
		String methodName = null;
		Method method = null;
		Class<?>[] types = null;
		Class<?> type = null;
		String value = null;
		while(names.hasMoreElements()) {	//还有下一个吗
			name = names.nextElement();
			
			methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1); 
			// 从setter方法中根据方法获取方法
			method = setter.get(methodName);
			
			if(method == null) {	//如果找不到对应的setter方法，说明这个属性没法注值，则不管
				continue;
			}
			
			//获取这个方法的形参类型
			types = method.getParameterTypes();
			if(types == null || types.length <= 0 ) {	//说明这个setter方法没有提供形参，则这个属性也不能注值
				continue;
			}
			
			//标准的setter方法应该只有一个形参
			type = types[0];	//取出第一个形参类型
			value = request.getParameter(name);
			if( Integer.class == type) {	//Integer
				method.invoke(t, Integer.valueOf(value));
			}else if( Integer.TYPE == type) {	//int
				method.invoke(t, Integer.parseInt(value));
			}else if( Double.class == type) {	//Double
				method.invoke(t, Double.valueOf(value));
			}else if( Double.TYPE == type) {	//double
				method.invoke(t, Double.parseDouble(value));
			}else if( Long.class == type) {	//Long
				method.invoke(t, Long.valueOf(value));
			}else if( Long.TYPE == type) {	//long
				method.invoke(t, Long.parseLong(value));
			}else {	//其余的都当成字符串处理
				method.invoke(t, value);
			}
		}
		
		// 注意，虽然表单里面这个是文件，但是实体类中我们一般定义为String,因为存在数据库端的是这个文件在服务器地址，而并不是文件信息
		//7、处理上传的文件
		// 获取所有上传的文件
		Files files = su.getFiles();	//注意，这个File不是IO的而是smartupload中的
		if(files == null || files.getCount() <= 0 || files.getSize() <= 0) {	//说明没有上传文件
			return t;
		}
		
		//如果带文件，这需要循环处理。注意不是一个input type = "file"中有多个文件，而是表单中有多个type="file"
		Collection<File> fls = files.getCollection();	//返回这个对象的集合表示，这样就好循环。这个File也不是io中的,而是smartupload中
		String fieldName = null;	//表单中name属性的值
		String filePath = null;		//文件保存路径
		String temp = null;
		String fileStr = "";
		
		for (File fl : fls) {
			if(! fl.isMissing()) {	//如果文件上传过程中没有丢失数据
				temp = fl.getFieldName();
				
				if(StringUtil.checkNull(fieldName)) {	//说明此时读取的是第一个文件
					fieldName = temp;
				}else {	// 说明不是第一个文件，这需要判断是不是跟上一个文件属于同一个文本框
					if(!temp.equals(fieldName)) {	// 说明换了一个文本框了
						methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
						method = setter.get(methodName);
						if(method == null) {
							continue;
						}
						
						//正常情况下，这个属性的类型应该是字符串，所以我这里省略了形参类型判断
						method.invoke(t, fileStr);
						fileStr = "";
						fieldName = temp;	//fieldName指向新的文本框的值
					}
				}
				
				//处理文件上传 -> 重命名文件，避免文件重名
				filePath = ConstantInfo.uploadpath + "/" + System.currentTimeMillis() + "_" + fl.getFileName();
				
				//上传，将文件存到指定的路径，注意这个路径必须是一个绝对路径
				fl.saveAs(ConstantInfo.basepath + "/" + filePath);
				
				//给图片添加水印
				//WaterMarkUtil.textWaterMark(new java.io.File(ConstantInfo.basepath + "/" + filePath));
				WaterMarkUtil.ImgWaterMark(new java.io.File(ConstantInfo.basepath + "/" + filePath));
				
				if(StringUtil.checkNull(fileStr)) {
					fileStr = filePath;
				}else {
					fileStr += ";" + filePath;
				}
			}
		}
		methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
		method = setter.get(methodName);
		if(method == null) {
			return t;
		}
		
		//正常情况下，这个属性的类型应该是字符串，所以我这里省略了形参类型判断
		method.invoke(t, fileStr);
		System.out.println(t);
		return t;
	}
}
